<?php

namespace PhpYes\Com;


class ClassLoader
{
    private static $isRegister = false;
    private static $prefixDirsPsr4;
    private static $class2FileArr = array();

    private static function register()
    {
        if (self::$isRegister) {
            return;
        }
        self::$isRegister = true;
        spl_autoload_register([ClassLoader::class, 'loadClass'], true);
    }

    public static function loadClass($class)
    {
        $file = empty(self::$class2FileArr[$class]) ? null : self::$class2FileArr[$class];
        if ($file && file_exists($file)) {
            require_once $file;

            return;
        }

        // PSR-4 lookup
        $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . '.php';

        $subPath = $class;
        while (false !== $lastPos = strrpos($subPath, '\\')) {
            $subPath = substr($subPath, 0, $lastPos);
            $search = $subPath . '\\';

            if (isset(self::$prefixDirsPsr4[$search])) {
                foreach (self::$prefixDirsPsr4[$search] as $dir) {
                    $file = $dir . DIRECTORY_SEPARATOR . substr($logicalPathPsr4, strlen($search));
                    if (file_exists($file)) {
                        self::$class2FileArr[$class] = $file;
                        require_once $file;

                        return;
                    }
                }
            }
        }
    }

    /**
     * Registers a set of PSR-4 directories for a given namespace, either
     * appending or prepending to the ones previously set for this namespace.
     *
     * @param string $prefix The prefix/namespace, with trailing '\\'
     * @param array|string $paths The PSR-4 base directories
     * @param bool $prepend Whether to prepend the directories
     *
     * @throws \InvalidArgumentException
     */
    public static function addPsr4($prefix, $paths, $prepend = false)
    {
        if (empty($prefix)) {
            return;
        }
        if (empty(self::$prefixDirsPsr4)) {
            self::$prefixDirsPsr4 = array();
        }

        if (!isset(self::$prefixDirsPsr4[$prefix])) {
            // Register directories for a new namespace.
            $length = strlen($prefix);
            if ('\\' !== $prefix[$length - 1]) {
                throw new \InvalidArgumentException("A non-empty PSR-4 prefix must end with a namespace separator.");
            }
            self::$prefixDirsPsr4[$prefix] = (array)$paths;
        } elseif ($prepend) {
            // Prepend directories for an already registered namespace.
            self::$prefixDirsPsr4[$prefix] = array_merge(
                (array)$paths,
                self::$prefixDirsPsr4[$prefix]
            );
        } else {
            // Append directories for an already registered namespace.
            self::$prefixDirsPsr4[$prefix] = array_merge(
                self::$prefixDirsPsr4[$prefix],
                (array)$paths
            );
        }

        if (!self::$isRegister) {
            self::register();
        }
    }

    /**
     * Unregisters a set of PSR-4 directories for a given namespace
     *
     * @param string $prefix - refer self::addPsr4($prefix)
     * @param array|string $paths - refer self::addPsr4($paths)
     */
    public static function rmPsr4($prefix, $paths)
    {
        if (empty($prefix) || empty($paths) || empty(self::$prefixDirsPsr4[$prefix])) {
            return;
        }

        $registerPaths = self::$prefixDirsPsr4[$prefix];
        $newPaths = array();
        $paths = (array)$paths;
        foreach ($registerPaths as $p) {
            if (!in_array($p, $paths)) {
                $newPaths[] = $p;
            }
        }

        if (empty($newPaths)) {
            unset(self::$prefixDirsPsr4[$prefix]);
            if (empty(self::$prefixDirsPsr4)) {
                spl_autoload_unregister([ClassLoader::class, 'loadClass']);
            }
        } else {
            self::$prefixDirsPsr4[$prefix] = $newPaths;
        }
    }

}